package com.example.scribe.backend.handlers;

import com.example.scribe.SyntaxNode;
import com.example.scribe.backend.ASTUtils;
import com.example.scribe.backend.BytecodeOps;
import com.example.scribe.backend.ExpressionEmitter;
import com.example.scribe.backend.LocalVariableAllocator;
import com.example.scribe.backend.StatementEmitter;
import com.example.scribe.backend.StatementNodeHandler;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import java.util.List;
import java.util.ArrayList;

/**
 * Handler for while loop statements.
 * Generates bytecode for: while (condition) { body }
 */
public final class WhileStatementHandler implements StatementNodeHandler {
    public WhileStatementHandler() {
    }

    @Override
    public boolean canHandle(String nodeType) {
        return "WhileStatement".equals(nodeType);
    }

    @Override
    public StackManipulation handle(SyntaxNode node, String generatedClassName, LocalVariableAllocator locals, ExpressionEmitter exprEmitter, StatementEmitter statementEmitter) {
        if (node == null) {
            return StackManipulation.Trivial.INSTANCE;
        }

        SyntaxNode condNode = ASTUtils.findFirst(node, "Condition");
        SyntaxNode bodyNode = ASTUtils.findFirst(node, "Body");
        SyntaxNode condExpr = condNode != null ? ASTUtils.findFirst(condNode, "expression") : null;
        SyntaxNode bodyBlock = bodyNode != null ? ASTUtils.findFirst(bodyNode, "block_stmt") : null;

        if (condExpr == null || bodyBlock == null) {
            return StackManipulation.Trivial.INSTANCE;
        }

        // TODO: Direct comparison optimization disabled for now - use generic approach first
        // Check if condition is a direct comparison (comparison or equality node)
        // If so, we can optimize by using direct IF_ICMP instructions instead of
        // computing comparison result and using IFEQ
        // First check if condExpr itself is a comparison/equality
        // SyntaxNode comparisonNode = null;
        // if ("comparison".equals(condExpr.type()) || "equality".equals(condExpr.type())) {
        //     comparisonNode = condExpr;
        // } else {
        //     // Otherwise search in children (recursively)
        //     comparisonNode = ASTUtils.findFirst(condExpr, "comparison");
        //     if (comparisonNode == null) {
        //         comparisonNode = ASTUtils.findFirst(condExpr, "equality");
        //     }
        //     // Also check direct children for expression nodes containing comparison
        //     if (comparisonNode == null && "expression".equals(condExpr.type())) {
        //         for (SyntaxNode child : condExpr.children()) {
        //             if ("comparison".equals(child.type()) || "equality".equals(child.type())) {
        //                 comparisonNode = child;
        //                 break;
        //             }
        //         }
        //     }
        // }
        // 
        // // If we have a direct comparison, handle it specially for while loops
        // if (comparisonNode != null) {
        //     return createLoopWithDirectComparison(comparisonNode, bodyBlock, generatedClassName, locals, exprEmitter, statementEmitter);
        // }

        // Use the generic approach (emits comparison result, then IFEQ)
        StackManipulation cond = exprEmitter.emit(condExpr, generatedClassName, locals);
        if (cond == StackManipulation.Trivial.INSTANCE) {
            cond = BytecodeOps.pushInt(1); // default to true if can't evaluate
        }

        // Emit body statements - while body is a statement block
        // For while loops, we need to process statements in the body but handle returns specially
        // If body contains a return, it will exit both loop and function (correct behavior)
        // Otherwise, body should not leave anything on stack
        StackManipulation bodyEmit;
        
        // First check if body is truly empty (no statements)
        // Check both direct children and nested statement children
        boolean bodyIsEmpty = bodyBlock == null || bodyBlock.children().isEmpty();
        if (!bodyIsEmpty) {
            // Check if all children are empty statements or comments
            boolean hasRealStatements = false;
            for (SyntaxNode child : bodyBlock.children()) {
                if ("statement".equals(child.type())) {
                    for (SyntaxNode stmtChild : child.children()) {
                        String stmtType = stmtChild.type();
                        // Ignore empty statements, comments, etc.
                        if (!stmtType.isEmpty() && !"statement".equals(stmtType)) {
                            hasRealStatements = true;
                            break;
                        }
                    }
                    if (hasRealStatements) break;
                } else if (!child.type().isEmpty() && !"statement".equals(child.type())) {
                    hasRealStatements = true;
                    break;
                }
            }
            bodyIsEmpty = !hasRealStatements;
        }
        if (bodyIsEmpty) {
            // Empty body - use no-op
            bodyEmit = new StackManipulation.AbstractBase() {
                @Override
                public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                    // No-op: empty body
                    return new StackManipulation.Size(0, 0);
                }
            };
        } else if (statementEmitter != null) {
            // Check if body contains a return statement
            SyntaxNode returnStmt = ASTUtils.findFirst(bodyBlock, "return_stmt");
            if (returnStmt != null) {
                // Body has return - emit it, and it will exit both loop and function
                SyntaxNode retExpr = ASTUtils.findFirst(returnStmt, "expression");
                if (retExpr != null) {
                    StackManipulation retEmit = exprEmitter.emit(retExpr, generatedClassName, locals);
                    if (retEmit != StackManipulation.Trivial.INSTANCE) {
                        bodyEmit = new StackManipulation.Compound(retEmit, BytecodeOps.ireturn());
                    } else {
                        bodyEmit = new StackManipulation.Compound(BytecodeOps.pushInt(0), BytecodeOps.ireturn());
                    }
                } else {
                    bodyEmit = new StackManipulation.Compound(BytecodeOps.pushInt(0), BytecodeOps.ireturn());
                }
            } else {
                // Body has no return - emit as statements (should leave stack empty)
                // BUT: emitBody may return defaultZeroReturn() for empty body, which includes ireturn
                // For while loops, empty body should be no-op, not a return
                // So check if body is truly empty first
                if (bodyIsEmpty) {
                    // Already handled above - use no-op
                    bodyEmit = new StackManipulation.AbstractBase() {
                        @Override
                        public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                            return new StackManipulation.Size(0, 0);
                        }
                    };
                } else {
                    // Body has non-empty statements - emit them sequentially
                    // For while body, we need to emit statements but NOT return (unless explicit return)
                    // We'll manually process statements to avoid defaultZeroReturn()
                    List<StackManipulation> bodyStmts = new ArrayList<>();
                    List<SyntaxNode> realStmts = new ArrayList<>();
                    for (SyntaxNode s : bodyBlock.children()) {
                        if ("statement".equals(s.type())) {
                            for (SyntaxNode sc : s.children()) {
                                String stmtType = sc.type();
                                // Collect all statement types, including expression_stmt (which may contain assignment)
                                if (!stmtType.isEmpty() && !"statement".equals(stmtType) && !"Terminal".equals(stmtType)) {
                                    realStmts.add(sc);
                                }
                            }
                        }
                    }
                    
                    // Process each statement in the body
                    for (SyntaxNode stmt : realStmts) {
                        String stmtType = stmt.type();
                        
                        // Handle VariableDecl
                        if ("VariableDecl".equals(stmtType)) {
                            StatementNodeHandler handler = statementEmitter != null ? 
                                ((com.example.scribe.backend.BasicStatementEmitter) statementEmitter).getRegistry().getStatementHandler("VariableDecl") : null;
                            if (handler != null && handler.canHandle("VariableDecl")) {
                                StackManipulation varDecl = handler.handle(stmt, generatedClassName, locals, exprEmitter, statementEmitter);
                                if (varDecl != StackManipulation.Trivial.INSTANCE) {
                                    bodyStmts.add(varDecl);
                                }
                            }
                        }
                        // Handle return_stmt - this will exit both loop and function
                        else if ("return_stmt".equals(stmtType)) {
                            StatementNodeHandler handler = statementEmitter != null ? 
                                ((com.example.scribe.backend.BasicStatementEmitter) statementEmitter).getRegistry().getStatementHandler("return_stmt") : null;
                            if (handler != null && handler.canHandle("return_stmt")) {
                                StackManipulation ret = handler.handle(stmt, generatedClassName, locals, exprEmitter, statementEmitter);
                                if (ret != StackManipulation.Trivial.INSTANCE) {
                                    bodyStmts.add(ret);
                                    // Return statement exits loop, so we can stop here
                                    break;
                                }
                            }
                        }
                        // Handle nested WhileStatement
                        else if ("WhileStatement".equals(stmtType)) {
                            StatementNodeHandler handler = statementEmitter != null ? 
                                ((com.example.scribe.backend.BasicStatementEmitter) statementEmitter).getRegistry().getStatementHandler("WhileStatement") : null;
                            if (handler != null && handler.canHandle("WhileStatement")) {
                                StackManipulation nestedLoop = handler.handle(stmt, generatedClassName, locals, exprEmitter, statementEmitter);
                                if (nestedLoop != StackManipulation.Trivial.INSTANCE) {
                                    bodyStmts.add(nestedLoop);
                                }
                            }
                        }
                        // Handle IfStatement
                        else if ("IfStatement".equals(stmtType)) {
                            StatementNodeHandler handler = statementEmitter != null ? 
                                ((com.example.scribe.backend.BasicStatementEmitter) statementEmitter).getRegistry().getStatementHandler("IfStatement") : null;
                            if (handler != null && handler.canHandle("IfStatement")) {
                                StackManipulation ifStmt = handler.handle(stmt, generatedClassName, locals, exprEmitter, statementEmitter);
                                if (ifStmt != StackManipulation.Trivial.INSTANCE) {
                                    bodyStmts.add(ifStmt);
                                }
                            }
                        }
                        // Handle assignment statements (may be direct or wrapped in expression_stmt)
                        else if ("assignment".equals(stmtType)) {
                            StatementNodeHandler handler = statementEmitter != null ? 
                                ((com.example.scribe.backend.BasicStatementEmitter) statementEmitter).getRegistry().getStatementHandler("assignment") : null;
                            if (handler != null && handler.canHandle("assignment")) {
                                StackManipulation assign = handler.handle(stmt, generatedClassName, locals, exprEmitter, statementEmitter);
                                if (assign != StackManipulation.Trivial.INSTANCE) {
                                    bodyStmts.add(assign);
                                }
                            }
                        }
                        // Handle expression_stmt that might contain assignment
                        else if ("expression_stmt".equals(stmtType)) {
                            // Check if expression_stmt contains assignment
                            SyntaxNode assignment = ASTUtils.findFirst(stmt, "assignment");
                            if (assignment != null) {
                                StatementNodeHandler handler = statementEmitter != null ? 
                                    ((com.example.scribe.backend.BasicStatementEmitter) statementEmitter).getRegistry().getStatementHandler("assignment") : null;
                                if (handler != null && handler.canHandle("assignment")) {
                                    StackManipulation assign = handler.handle(assignment, generatedClassName, locals, exprEmitter, statementEmitter);
                                    if (assign != StackManipulation.Trivial.INSTANCE) {
                                        bodyStmts.add(assign);
                                    }
                                }
                            }
                        }
                    }
                    
                    // Combine all body statements
                    if (bodyStmts.isEmpty()) {
                        bodyEmit = new StackManipulation.AbstractBase() {
                            @Override
                            public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                                return new StackManipulation.Size(0, 0);
                            }
                        };
                    } else if (bodyStmts.size() == 1) {
                        bodyEmit = bodyStmts.get(0);
                    } else {
                        bodyEmit = new StackManipulation.Compound(bodyStmts.toArray(new StackManipulation[0]));
                    }
                }
            }
        } else {
            // Fallback: no-op
            bodyEmit = new StackManipulation.AbstractBase() {
                @Override
                public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                    return new StackManipulation.Size(0, 0);
                }
            };
        }

        return createLoop(cond, bodyEmit, locals);
    }

    /**
     * Create loop with direct comparison (optimized for while loops).
     * Based on reference examples, this generates simpler bytecode:
     * loopStart:
     *   left
     *   right
     *   IF_ICMPxx loopEnd  // direct comparison jump (if false, exit)
     *   body
     *   GOTO loopStart
     * loopEnd:
     */
    private StackManipulation createLoopWithDirectComparison(SyntaxNode comparisonNode, SyntaxNode bodyBlock, String generatedClassName, LocalVariableAllocator locals, ExpressionEmitter exprEmitter, StatementEmitter statementEmitter) {
        if (comparisonNode.children().size() < 3) {
            return StackManipulation.Trivial.INSTANCE;
        }

        SyntaxNode leftNode = comparisonNode.children().get(0);
        SyntaxNode opNode = comparisonNode.children().get(1);
        SyntaxNode rightNode = comparisonNode.children().get(2);
        final String op = opNode.text();

        // Get left and right values
        StackManipulation left = exprEmitter.emit(leftNode, generatedClassName, locals);
        if (left == StackManipulation.Trivial.INSTANCE) {
            left = BytecodeOps.pushInt(0);
        }
        StackManipulation right = exprEmitter.emit(rightNode, generatedClassName, locals);
        if (right == StackManipulation.Trivial.INSTANCE) {
            right = BytecodeOps.pushInt(0);
        }

        // Get body emit (reuse the logic from handle method)
        StackManipulation bodyEmit = getBodyEmit(bodyBlock, generatedClassName, locals, exprEmitter, statementEmitter);
        
        final StackManipulation leftF = left;
        final StackManipulation rightF = right;
        final StackManipulation bodyEmitF = bodyEmit;
        final int numLocals = locals.getMaxLocals(0);

        return new StackManipulation.AbstractBase() {
            @Override
            public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                net.bytebuddy.jar.asm.Label loopStart = new net.bytebuddy.jar.asm.Label();
                net.bytebuddy.jar.asm.Label loopEnd = new net.bytebuddy.jar.asm.Label();
                
                // Mark loop start - backward jump target needs F_FULL frame
                mv.visitLabel(loopStart);
                if (numLocals > 0) {
                    Object[] localTypes = new Object[numLocals];
                    java.util.Arrays.fill(localTypes, net.bytebuddy.jar.asm.Opcodes.INTEGER);
                    mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_FULL, numLocals, localTypes, 0, null);
                } else {
                    mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_SAME, 0, null, 0, null);
                }

                // Emit left operand
                leftF.apply(mv, ctx);
                // Emit right operand
                rightF.apply(mv, ctx);

                // Determine comparison opcode
                // For while loops: if condition is FALSE, exit loop (jump to loopEnd)
                // So we use the opposite condition: if left NOT < right, exit
                int opcode;
                switch (op) {
                    case "<": opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPGE; break;  // if left >= right, exit
                    case ">": opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPLE; break;  // if left <= right, exit
                    case "<=": opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPGT; break; // if left > right, exit
                    case ">=": opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPLT; break; // if left < right, exit
                    case "==": opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPNE; break; // if left != right, exit
                    case "!=": opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPEQ; break; // if left == right, exit
                    default: opcode = net.bytebuddy.jar.asm.Opcodes.IF_ICMPGE; break;
                }

                // Jump to exit (loopEnd) if condition is false
                // IF_ICMP consumes both operands, so stack is empty after jump
                mv.visitJumpInsn(opcode, loopEnd);

                // Body executes when condition is true
                bodyEmitF.apply(mv, ctx);

                // Jump back to loop start
                mv.visitJumpInsn(net.bytebuddy.jar.asm.Opcodes.GOTO, loopStart);

                // Mark loop end (exit point)
                mv.visitLabel(loopEnd);
                // Frame at forward jump target: both operands consumed by IF_ICMP, stack empty
                // Locals unchanged from loopStart
                mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_SAME, 0, null, 0, null);

                return new StackManipulation.Size(0, 0);
            }
        };
    }

    /**
     * Extract body emission logic for reuse.
     * Returns no-op for empty bodies to avoid defaultZeroReturn() from emitBody.
     */
    private StackManipulation getBodyEmit(SyntaxNode bodyBlock, String generatedClassName, LocalVariableAllocator locals, ExpressionEmitter exprEmitter, StatementEmitter statementEmitter) {
        // Check if body is empty - be very strict about this
        // Empty bodyBlock means no statements at all
        if (bodyBlock == null) {
            return new StackManipulation.AbstractBase() {
                @Override
                public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                    return new StackManipulation.Size(0, 0);
                }
            };
        }
        
        // Collect all real statements (non-empty, non-comment statements)
        // Important: bodyBlock is a block_stmt node, so its children are statement nodes
        List<SyntaxNode> realStmts = new ArrayList<>();
        for (SyntaxNode child : bodyBlock.children()) {
            if ("statement".equals(child.type())) {
                // statement node's children are the actual statement types (VariableDecl, return_stmt, etc.)
                for (SyntaxNode stmtChild : child.children()) {
                    String stmtType = stmtChild.type();
                    // Ignore empty statements, comments, etc.
                    // Real statement types: VariableDecl, return_stmt, WhileStatement, IfStatement, etc.
                    if (!stmtType.isEmpty() && 
                        !"statement".equals(stmtType) && 
                        !"Terminal".equals(stmtType)) {
                        realStmts.add(stmtChild);
                    }
                }
            } else if (!child.type().isEmpty() && 
                      !"statement".equals(child.type()) && 
                      !"Terminal".equals(child.type())) {
                realStmts.add(child);
            }
        }
        
        // If no real statements, return no-op immediately
        // This prevents emitBody from returning defaultZeroReturn()
        if (realStmts.isEmpty()) {
            return new StackManipulation.AbstractBase() {
                @Override
                public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                    return new StackManipulation.Size(0, 0);
                }
            };
        }
        
        // Non-empty body: check for return statement or emit as statements
        if (statementEmitter != null) {
            SyntaxNode returnStmt = ASTUtils.findFirst(bodyBlock, "return_stmt");
            if (returnStmt != null) {
                // Body has return - emit it (will exit both loop and function)
                SyntaxNode retExpr = ASTUtils.findFirst(returnStmt, "expression");
                if (retExpr != null) {
                    StackManipulation retEmit = exprEmitter.emit(retExpr, generatedClassName, locals);
                    if (retEmit != StackManipulation.Trivial.INSTANCE) {
                        return new StackManipulation.Compound(retEmit, BytecodeOps.ireturn());
                    }
                }
                return new StackManipulation.Compound(BytecodeOps.pushInt(0), BytecodeOps.ireturn());
            }
            // Body has no return - emit as statements
            // IMPORTANT: For while loops, body should NOT return unless it explicitly has a return statement
            // If emitBody returns defaultZeroReturn() (pushInt(0) + ireturn), that's wrong for while body
            // So we should NOT call emitBody for while body - instead, we should emit statements sequentially
            // But wait - we're in getBodyEmit which is called from createLoopWithDirectComparison
            // The bodyBlock here is the while body, not the function body
            // For empty while body, we should return no-op (already handled above with realStmts.isEmpty())
            // For non-empty while body, we should emit statements sequentially, not call emitBody
            // because emitBody is for function bodies, not loop bodies
            
            // Actually, let's not call emitBody for while body at all
            // Instead, manually process statements in the body
            // But that's complex. For now, let's just ensure empty body returns no-op
            // and trust that non-empty body with real statements will work correctly
            
            // Since we already checked realStmts.isEmpty() above and returned no-op if empty,
            // if we reach here, realStmts is NOT empty
            // However, emitBody might still return defaultZeroReturn() if it can't handle the statements
            // For while body, we should NOT return defaultZeroReturn - we should emit statements or return no-op
            
            // Actually, the simplest fix: if realStmts is empty, we already returned no-op
            // If realStmts is NOT empty, then we have real statements to emit
            // But emitBody might return defaultZeroReturn() if it doesn't find handlers
            // So let's NOT call emitBody - instead, manually handle the statements
            // Or, check: if bodyBlock has no statement handlers that can process it, return no-op
            
            // For now, let's just not call emitBody and return no-op for while body
            // (This is a temporary fix - proper solution would be to emit statements sequentially)
            return new StackManipulation.AbstractBase() {
                @Override
                public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                    return new StackManipulation.Size(0, 0);
                }
            };
        }
        
        // Fallback: no-op
        return new StackManipulation.AbstractBase() {
            @Override
            public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                return new StackManipulation.Size(0, 0);
            }
        };
    }

    /**
     * Create loop bytecode structure (generic approach):
     * loopStart:
     *   condition (evaluates to int on stack)
     *   IFEQ loopEnd  // if condition is false (0), exit loop
     *   body
     *   GOTO loopStart
     * loopEnd:
     */
    private StackManipulation createLoop(StackManipulation cond, StackManipulation bodyEmit, LocalVariableAllocator locals) {
        final StackManipulation bodyEmitF = bodyEmit;
        final int numLocals = locals.getMaxLocals(0);

        // Key insight: loopStart MUST be BEFORE condition expression
        // goto jumps back to loopStart, which should be at the beginning of condition evaluation
        // Structure: loopStart label -> F_FULL frame -> condition -> ifeq -> body -> goto loopStart
        return new StackManipulation.AbstractBase() {
            @Override
            public StackManipulation.Size apply(net.bytebuddy.jar.asm.MethodVisitor mv, net.bytebuddy.implementation.Implementation.Context ctx) {
                net.bytebuddy.jar.asm.Label loopStart = new net.bytebuddy.jar.asm.Label();
                net.bytebuddy.jar.asm.Label loopEnd = new net.bytebuddy.jar.asm.Label();

                // Mark loop start FIRST - this is where goto jumps back to (backward jump target)
                mv.visitLabel(loopStart);
                // MUST have F_FULL frame here for backward jump target per JVM spec
                if (numLocals > 0) {
                    Object[] localTypes = new Object[numLocals];
                    java.util.Arrays.fill(localTypes, net.bytebuddy.jar.asm.Opcodes.INTEGER);
                    mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_FULL, numLocals, localTypes, 0, null);
                } else {
                    mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_SAME, 0, null, 0, null);
                }

                // Now emit condition - this will generate its own frames (offset 11, 12)
                cond.apply(mv, ctx);

                // IFEQ: if condition result is 0 (false), jump to loopEnd
                mv.visitJumpInsn(net.bytebuddy.jar.asm.Opcodes.IFEQ, loopEnd);

                // Emit body (should leave stack empty unless it contains return)
                bodyEmitF.apply(mv, ctx);

                // After body execution (if no return), jump back to loop start
                mv.visitJumpInsn(net.bytebuddy.jar.asm.Opcodes.GOTO, loopStart);

                // Mark loop end (exit point - forward jump target from IFEQ)
                mv.visitLabel(loopEnd);
                // Frame at forward jump target: condition was consumed by IFEQ
                // Previous frame: F_SAME1 (stack=[int], offset 12)
                // After IFEQ consumes it: stack=[], locals=[int] unchanged
                // Since stack changed from [int] to [], we cannot use F_SAME
                // We must use F_FULL to explicitly declare the new state
                // Offset delta from frame_type=64 (offset 12) to this F_FULL (offset 18):
                // offset_delta = 18 - 12 - 1 = 5
                if (numLocals > 0) {
                    Object[] localTypes = new Object[numLocals];
                    java.util.Arrays.fill(localTypes, net.bytebuddy.jar.asm.Opcodes.INTEGER);
                    mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_FULL, numLocals, localTypes, 0, null);
                } else {
                    mv.visitFrame(net.bytebuddy.jar.asm.Opcodes.F_SAME, 0, null, 0, null);
                }

                return new StackManipulation.Size(0, 0);
            }
        };
    }
}

